#include <cstdlib>
#include <cmath>
#include <cassert>
#include <GL/gl.h>
#include <GL/glu.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <vector>
#include <cstring>
#include <string>

struct stRGB
{
   unsigned char R;
   unsigned char G;
   unsigned char B;
   unsigned char A;
};
typedef stRGB tpRGB;


struct stConfig
{
   float deltaT,
         deltaX,
         deltaY,
         velocity;
         
   int   gpu,   
         saveStep,      //Se é para gravar log, de quanto em quanto tempo
         domainX,      //dominio em X, Y
         domainY;
   
   bool setSaveLogFile; //Grava arquivo de log
   
   unsigned int maxSimulationTime;
};
typedef stConfig tpConfig;

extern "C" void initGPU(int,
                        unsigned int,
                        unsigned int,
                        float,
                        float);

extern "C" void runWave(float, 
                        float * );  
extern "C" void freeGPU(void);
 
#define PI 3.141592653589793238462643383279502884197f
#define CFL 1.0f / sqrt(2.0f)

tpConfig mConfig;
SDL_Surface           *mScreen,   
                      *mPicture;
SDL_Event              mEvent;
SDL_Rect               mPictureLocation;
const SDL_VideoInfo*   mVideoinfo;

std::vector<float>    mDomain;
bool mIsRunning               = true;
unsigned int mSimulationTime  = 0 ;
char         mFileName[256];
FILE        *mPtrFile         = NULL;
double      mMaxMem = 0.0f,
            mMaxGPU = 0.0f;

float mAlpha    = 0.0f;
float mBeta     = 0.0f;

////////////////////////////////////////////////////////////////////////////////
//
// Função de ajuda
//
////////////////////////////////////////////////////////////////////////////////
void showHelp(void)
{

   fprintf(stderr, "\n\tAJUDA:");
   fprintf(stderr, "\n\t\t%s s 10 m 100 t 0.0000150 x 0.000390625 y 0.000390625 w 128 h 128 v 15 g 0\n", _APPLICATION);
   fprintf(stderr, "\n\t a/A : Ajuda - exibe este texto");
   fprintf(stderr, "\n\t s/S : Tempo de simulção para salvar em logs ( < 0 não grava )");
   fprintf(stderr, "\n\t m/M : Tempo total de simulação");
   fprintf(stderr, "\n\t t/T : Delta T");
   fprintf(stderr, "\n\t x/X : Delta X");
   fprintf(stderr, "\n\t y/Y : Delta Y");
   fprintf(stderr, "\n\t w/W : Tamanho da grade em X");
   fprintf(stderr, "\n\t h/H : Tamanho da grade em Y");
   fprintf(stderr, "\n\t g/G : Inidica a GPU para mais de 1");
   fprintf(stderr, "\n\n\t\t Universidade Federal Fluminense - Medialab");
   fprintf(stderr, "\n\n\t\t Versão: %s", _VERSION);
   fprintf(stderr, "\n\n");
}

////////////////////////////////////////////////////////////////////////////////
//
// Função para pegar os argumentos em qualquer ordem
//
////////////////////////////////////////////////////////////////////////////////
void getParameters(int argc, char **argv)
{

   int param = 0;
   char opt;

   mConfig.setSaveLogFile = true;
   
   for (unsigned int i = 1; i < argc; i+=2)
   {
      opt = argv[i][0];
      switch (opt)
      {
         case 'a':
         case 'A': showHelp(); exit(EXIT_FAILURE); break;

         case 'w':
         case 'W': mConfig.domainX = atoi(argv[i+1]); param++; break;      //Dominio em X

         case 'h':
         case 'H': mConfig.domainY = atoi(argv[i+1]); param++; break;      //Dominio em Y

         case 'x':
         case 'X': mConfig.deltaX = atof(argv[i+1]); param++; break;      //delta X

         case 'y':
         case 'Y': mConfig.deltaY = atof(argv[i+1]); param++; break;      //delta Y

         case 't':
         case 'T': mConfig.deltaT = atof(argv[i+1]); param++; break;      //delta T

         case 's':
         case 'S': mConfig.saveStep = atoi(argv[i+1]); param++; break;      //Tempo de simulacao de gravação

         case 'm':
         case 'M': mConfig.maxSimulationTime = atoi(argv[i+1]); param++; break;      //Tempo de simulacao de gravação
  
         case 'g':
         case 'G': mConfig.gpu = atoi(argv[i+1]); param++; break;      //Tempo de simulacao de gravação

         //case 'l':
         //case 'L': strcpy(logFileName, argv[i+1]); param++; break; //Nome do arquivo de log 
        
         case 'v':
         case 'V': mConfig.velocity = atof(argv[i+1]); param++; break; //Nome do arquivo com campo de velocidade 
      }      
   }
   
   if (param != 9)
   {
      showHelp();
      //Valores default
      mConfig.gpu = 0;
      mConfig.deltaT = 0.0000150f;
      mConfig.deltaX = mConfig.deltaY = 0.000390625f;
      mConfig.velocity = 15.0f;
      mConfig.saveStep = 10;
      mConfig.domainX = 512;
      mConfig.domainY = 512;
      mConfig.setSaveLogFile = true;
      mConfig.maxSimulationTime = 100;
#ifdef _DEBUG
     fprintf(stdout, "\nNúmero de parâmetros: %d", param);
#endif
   }
   
   if(mConfig.saveStep < 1)
   {
      mConfig.setSaveLogFile = false;
      mConfig.saveStep = 1;
   }
      
   fprintf(stdout, "\nParâmetros definidos:");
   fprintf(stdout, "\nDiscretização (%d, %d) delta (x = %f, y = %f, t = %f)", 
             mConfig.domainX , mConfig.domainY, mConfig.deltaX, mConfig.deltaY, mConfig.deltaT);
   fprintf(stdout, "\nTempo de simulação %d / Tempo do log %d", mConfig.maxSimulationTime, mConfig.saveStep);

   mAlpha = (mConfig.deltaT  * mConfig.deltaT) / (mConfig.deltaX * mConfig.deltaX);
   mAlpha *= mConfig.velocity * mConfig.velocity;

   mBeta = (mConfig.deltaT  * mConfig.deltaT) / (mConfig.deltaY * mConfig.deltaY);
   mBeta *= mConfig.velocity * mConfig.velocity;


   if (sqrt(mAlpha) > CFL)
      fprintf(stderr, "\n[ERROR] ESTABILIDADE DO METODO!!!!"); 



   fprintf(stdout, "\nDeltaT = %f / DeltaX = %f", mConfig.deltaT, mConfig.deltaX);
   fprintf(stdout, "\nDeltaT = %f / DeltaY = %f", mConfig.deltaT, mConfig.deltaY);

   fprintf(stdout, "\nCourant = %f", sqrt(mAlpha));
   fflush(stdout);

   mDomain.resize(mConfig.domainX * mConfig.domainY);

   initGPU(mConfig.gpu, mConfig.domainX, mConfig.domainY, mAlpha, mBeta); //dx_ref
   
  
}

////////////////////////////////////////////////////////////////////////////////
//
// Função para inicializar a parte de visualização
//
////////////////////////////////////////////////////////////////////////////////
void init(void)
{
   atexit(SDL_Quit);

   /* Initialize the SDL library */
   if( SDL_Init(SDL_INIT_VIDEO) < 0 )
   {
      fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError());
      exit(EXIT_FAILURE);
   }


unsigned int rmask, gmask, bmask, amask;
 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
   rmask = 0xff000000;
   gmask = 0x00ff0000;
   bmask = 0x0000ff00;
   amask = 0x000000ff;
 #else
   rmask = 0x000000ff;
   gmask = 0x0000ff00;
   bmask = 0x00ff0000;
   amask = 0xff000000;
#endif

   mPicture  = SDL_CreateRGBSurface(SDL_SWSURFACE,
                                    mConfig.domainX, mConfig.domainY,32,
                                    rmask, gmask, bmask, amask ) ; //IMG_Load("lena.bmp");

   if (mPicture == NULL)
   {
      fprintf(stderr, "Couldn't load %s: %s\n", "lena.bmp", SDL_GetError());
      exit(EXIT_FAILURE);
   }

   
   mScreen = SDL_SetVideoMode(mPicture->w, mPicture->h, mPicture->format->BitsPerPixel, SDL_DOUBLEBUF | SDL_HWSURFACE);
   if ( mScreen == NULL ) 
   {
      fprintf(stderr, "Couldn't set %dx%dx%d video mode: %s\n", mPicture->w, mPicture->h, mPicture->format->BitsPerPixel, SDL_GetError());
      exit(EXIT_FAILURE);
   }

   mVideoinfo = SDL_GetVideoInfo();

   printf("%i", mVideoinfo->blit_hw);

  
   mPictureLocation.x = 0;
   mPictureLocation.y = 0;

   
}


////////////////////////////////////////////////////////////////////////////////
//
// Função para capturar entrada do usuário
//
////////////////////////////////////////////////////////////////////////////////
inline void input(void)
{
   unsigned char *ptr = NULL;
   if( SDL_PollEvent( &mEvent ) )
   {

      switch( mEvent.type )
      {
         case SDL_KEYDOWN:
            switch(mEvent.key.keysym.sym)
            {
               case SDLK_ESCAPE: mIsRunning = false; break;
               case SDLK_p:
                  fprintf(stdout, "\nImage: (%d, %d, %d)", mPicture->h, mPicture->h, mPicture->format->BytesPerPixel);
                  fflush(stdout);
               break;
               case SDLK_a: 
                  fprintf(stdout, "\n%d", mPicture->format->BytesPerPixel);
                  fflush(stdout);
                  ptr = reinterpret_cast <unsigned char *> (mPicture->pixels);
                  for (unsigned int i = 0; i < mPicture->h ; i++)
                  {
                     for (unsigned int j = 0; j < mPicture->w ; j++)
                     {
                        unsigned int k = mPicture->format->BytesPerPixel * ( (i * mPicture->w) + j);
                        ptr[k]   = 0;  //R
                        ptr[k+1] = 0;  //G 
                        ptr[k+2] = 0;  //B
                        ptr[k+3] = 255;//A 
                     }
                     
                  }
                  break;
    

            }//end-switch(event.key.keysym.sym)
         break;
      }//end-switch( mEvent.type )
   }//end-if( SDL_PollEvent( &mEvent ) )
}

////////////////////////////////////////////////////////////////////////////////
//
// Função definir cor para intensidade da onda
//
////////////////////////////////////////////////////////////////////////////////
tpRGB ColorFromFloat(float vmin, float vmax, float v)
 {
	if (v > vmax) v = vmax;
	if (v < vmin) v = vmin;
	// Calculate local position within the [vmin,vmax] interval
	v -= vmin;
	v /= vmax - vmin;
	// Apply color mapping based on local position
	tpRGB RGB;
	
	if  (v > 7.0f/8.0f) {
		RGB.R = static_cast <unsigned char> (255.0f - 128.0f*(8.0f*v - 7.0f));
		RGB.G = static_cast <unsigned char> (0.0f);
		RGB.B = static_cast <unsigned char> (0.0f);
	}
	else if (v > 6.0f/8.0f) {
		RGB.R = static_cast <unsigned char> (255.0f);
		RGB.G = static_cast <unsigned char> (128.0f - 128.0f*(8.0f*v - 6.0f));
		RGB.B = static_cast <unsigned char> (0.0f);
	}
	else if (v > 5.0f/8.0f) {
		RGB.R = static_cast <unsigned char> (255.0f);
		RGB.G = static_cast <unsigned char> (255.0f - 128.0f*(8.0f*v - 5.0f));
		RGB.B = static_cast <unsigned char> (0.0f);
	}
	else if (v > 4.0f/8.0f) {
		RGB.R = static_cast <unsigned char> (127.0f + 128.0f*(8.0f*v - 4.0f));
		RGB.G = static_cast <unsigned char> (255.0f);
		RGB.B = static_cast <unsigned char> (128.0f - 128.0f*(8.0f*v - 4.0f));
	}
	else if (v > 3.0f/8.0f) {
		RGB.R = static_cast <unsigned char> (128.0f*(8.0f*v - 3.0f));
		RGB.G = static_cast <unsigned char> (255.0f);
		RGB.B = static_cast <unsigned char> (255.0f - 128.0f*(8.0f*v - 3.0f));
	}
	else if (v > 2.0f/8.0f) {
		RGB.R = static_cast <unsigned char> (0.0f);
		RGB.G = static_cast <unsigned char> (127.0f + 128.0f*(8.0f*v - 2.0f));
		RGB.B = static_cast <unsigned char> (255.0f);
	}
	else if (v > 1.0f/8.0f) {
		RGB.R = static_cast <unsigned char> (0.0f);
		RGB.G = static_cast <unsigned char> (128.0f*(8.0f*v - 1.0f));
		RGB.B = static_cast <unsigned char> (255.0f);
	}
	else {
		RGB.R = static_cast <unsigned char> (0.0f);
		RGB.G = static_cast <unsigned char> (0.0f);
		RGB.B = static_cast <unsigned char> (127.0f + 128.0f*8.0f*v);
	}
	RGB.A = 255;
	// Convert to unsigned int
//	return 255<<24 | (unsigned char)R<<16 | (unsigned char)G<<8 | (unsigned char)B;
	return RGB; //255<<24 | (unsigned char)R<<16 | (unsigned char)G<<8 | (unsigned char)B;
}

////////////////////////////////////////////////////////////////////////////////
//
// Função pulso
//
////////////////////////////////////////////////////////////////////////////////
float PulseFunction(float n)
{
   float f = 10.0f;
   float A = pow( (PI * f * n),2.0f);
   float B = exp(A * -1.0f);
   float C = 1.0f - 2.0f * A;
   float ret = C * B;
   float err = 1e-6f;
   if (fabs(ret) < err)
      ret = 0.0f;
   return ret;
   
}

////////////////////////////////////////////////////////////////////////////////
//
// Função de atualização do estado da simulação
//
////////////////////////////////////////////////////////////////////////////////
inline void update(void)
{
       unsigned char *ptr;
       tpRGB RGB;
       bool save = mConfig.setSaveLogFile;
       double memTime, GPUTime;
       bool  show = false;
 
       if (mSimulationTime <= mConfig.maxSimulationTime)
       {
            
         runWave(PulseFunction(static_cast <float>(mSimulationTime)), &(mDomain[0]));

       

         mSimulationTime++; //=mDeltaT;
         mConfig.saveStep++;
         if (save)
         {
            sprintf(mFileName, "%d.txt", mSimulationTime);
            fprintf(stdout, "\n Saving %s", mFileName);
            fflush(stdout);
            mPtrFile = fopen(mFileName, "w+");
            
         }
                  
         ptr = reinterpret_cast <unsigned char *> (mPicture->pixels);
         for (unsigned int i = 0; i < mPicture->h ; i++)
         {
            for (unsigned int j = 0; j < mPicture->w ; j++)
            {
               unsigned int l = (i * mPicture->w) + j;
               unsigned int k = mPicture->format->BytesPerPixel * l;

               RGB = ColorFromFloat(-1.01f, 3.01f, mDomain[l]);

               if (save)
                  fprintf(mPtrFile, "%d %d %.7f\n", j, i, mDomain[l]);

               ptr[k]   = RGB.R;
               ptr[k+1] = RGB.G;
               ptr[k+2] = RGB.B;
               ptr[k+3] = RGB.A;
  
            }
            
         }
         
         if (save)
         {
            fclose(mPtrFile);
             
         }
}
 
   SDL_FillRect(mScreen, NULL,  1000);
   SDL_BlitSurface( mPicture , NULL, mScreen, &mPictureLocation);
   SDL_Flip(mScreen);
}

////////////////////////////////////////////////////////////////////////////////
//
// Função do loop principal
//
////////////////////////////////////////////////////////////////////////////////
void mainLoop(void)
{
   while(mIsRunning) 
   {
      input();
      update();
   }
  
}

////////////////////////////////////////////////////////////////////////////////
//
// Função principal
//
////////////////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[])
{
   getParameters(argc, argv);
   init();
   mainLoop();   
   freeGPU();
   return EXIT_SUCCESS;
}



